package com.tiantian.minio.utils;

import com.tiantian.common.core.exception.BusinessException;
import com.tiantian.minio.config.properties.MinioProp;
import com.tiantian.minio.enums.ByteUnit;
import io.minio.*;
import io.minio.messages.DeleteError;
import io.minio.messages.DeleteObject;
import io.minio.messages.Item;
import lombok.Cleanup;
import lombok.RequiredArgsConstructor;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;

import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.text.SimpleDateFormat;
import java.util.*;


@Slf4j
@Service
@RequiredArgsConstructor
public class JxFileUtils {

    private final MinioClient minioClient;
    private final MinioProp minioProp;
    private final static String separator = "/";

    /**
     * 查询bucket中文件列表信息
     *
     * @param bucketName 桶名称
     * @param prefix     前缀(支持前缀模糊查询)
     * @return Iterable<Result < Item>>
     */
    @SneakyThrows
    public List<Item> listObjects(String bucketName, String prefix) {
        List<Item> items = new ArrayList<>();
        try {
            // 获取bucket中文件列表信息
            Iterable<Result<Item>> results = minioClient.listObjects(
                    ListObjectsArgs.builder()
                            .bucket(bucketName)
                            .maxKeys(100)            // 查询最大数量，默认1000
                            .includeVersions(false)  // 是否带版本号，如果为true，且文件没有版本号则会报错
                            .prefix(prefix)          // 查询文件名以"m"开头的文件
                            .build());
            for (Result<Item> result : results) {
                items.add(result.get());
            }
            return items;

        } catch (Exception ex) {
            log.error("Minio查询bucket中文件列表信息出现异常,bucketName: {}, prefix: {}", bucketName, prefix);
            return null;
        }
    }

    /**
     * 文件上传服务
     *
     * @param file       文件
     * @param bucketName 桶名称
     * @param extension  文件后缀
     * @return 文件地址
     */
    @SneakyThrows
    public String uploadObject(MultipartFile file, String extension, String bucketName) {
        // 检查文件大小(不超过2M)
        checkFileSize(file.getSize(), 2, ByteUnit.M);

        // 判断bucket是否存在 不存在则创建
        bucketExistsAndCreate(bucketName);

        // 构造目录
        String objectName = new SimpleDateFormat("yyyy/MM/dd/").format(new Date())
                + UUID.randomUUID().toString().replaceAll("-", "")
                + "." + extension;

        @Cleanup
        InputStream inputStream = file.getInputStream();

        // Object上传服务
        minioClient.putObject(PutObjectArgs.builder()
                .object(objectName) // 目录+文件名称
                .bucket(bucketName) // 桶名称
                .stream(inputStream, file.getSize(), -1)
                .contentType(file.getContentType()) // 文件类型
                .build());

        // 返回文件地址
        return minioProp.getEndpoint() + separator + bucketName + separator + objectName;

    }

    /**
     * 大文件上传服务
     *
     * @param file       文件
     * @param bucketName 桶名称
     */
    @SneakyThrows
    @Deprecated
    public String uploadFile(MultipartFile file, String bucketName) {
        // 创建一个临时文件
        File tempFile = File.createTempFile("file_upload", ".temp");

        // 将临时文件转存在指定位置
        file.transferTo(tempFile);
        String localFilePath = tempFile.getAbsolutePath();

        // 构造目录
        String fileName = file.getOriginalFilename();
        String objectName = new SimpleDateFormat("yyyy/MM/dd/").format(new Date())
                + UUID.randomUUID().toString().replaceAll("-", "")
                + fileName.substring(fileName.lastIndexOf("."));

        // 将本地文件上传到minio
        minioClient.uploadObject(UploadObjectArgs.builder()
                .bucket(bucketName)      // 桶名称
                .filename(localFilePath) // 指定本地文件路径
                .object(objectName)        // 对象名 放在子目录下
                .contentType(file.getContentType())
                .build());

        return minioProp.getEndpoint() + separator + bucketName + separator + objectName;
    }

    /**
     * 下载文件
     *
     * @param bucketName 桶名称
     * @param objectName 文件名称
     */
    @SneakyThrows()
    public InputStream downloadFile(String bucketName, String objectName) {
        return minioClient.getObject(GetObjectArgs.builder()
                .bucket(bucketName)
                .object(objectName)   // 文件名称(以桶名称为根路径)
                .build());
    }


    /**
     * 下载服务
     *
     * @param bucketName 桶名称
     * @param objectName 要删除的桶下根目录的对象/子目录下的对象 格式: 文件(path/file.png)   文件夹(path/to/)
     */
    @SneakyThrows()
    public void downloadObject(String bucketName, String objectName) {
        // Object下载服务,判断Object文件是否存在
        Iterable<Result<Item>> results = minioClient.listObjects(
                ListObjectsArgs.builder()
                        .bucket(bucketName)
                        .build());

        boolean objectfound = false;
        for (Result<Item> result : results) {
            Item item = result.get();
            if (Objects.equals(item.objectName(), objectName)) {
                objectfound = true;
                break;
            }
        }
        // 当Object文件存在时，则下载到项目根目录
        if (objectfound) {

            minioClient.downloadObject(
                    DownloadObjectArgs.builder()
                            .bucket(bucketName)
                            .object(objectName)
                            .filename(objectName)
                            .build());// 下载到当前项目目录

            log.info("objectName:" + objectName + " is downloaded in local project folder");
        } else {
            log.info("objectName:" + objectName + " does not exist");
        }

    }


    /**
     * 删除服务
     *
     * @param bucketName 桶名称
     * @param objectName 要删除的桶下根目录的对象/子目录下的对象 格式: 文件(path/file.png)   文件夹(path/to/)
     * @return 是否删除成功
     */
    @SneakyThrows
    public boolean removeObject(String bucketName, String objectName) {
        minioClient.removeObject(RemoveObjectArgs.builder()
                .bucket(bucketName)
                .object(objectName)
                .build());
        return true;
    }

    /**
     * 批量删除服务
     *
     * @param bucketName  桶名称
     * @param objectNames 要删除的桶下根目录的对象/子目录下的对象 格式: 文件(path/file.png)   文件夹(path/to/)
     */
    @SneakyThrows()
    public void removeObjects(String bucketName, String[] objectNames) {
        // 存入需要删除的文件名集合
        List<DeleteObject> objects = new LinkedList<>();
        for (String s : objectNames) {
            objects.add(new DeleteObject(s));
        }
        // Object批量删除服务
        Iterable<Result<DeleteError>> results = minioClient.removeObjects(
                RemoveObjectsArgs.builder()
                        .bucket(bucketName)
                        .objects(objects)
                        .build());
        if (results.iterator().hasNext()) {
            for (Result<DeleteError> result : results) {
                DeleteError error = result.get();
                log.info("Error in deleting object " + error.objectName() + "; " + error.message());
            }
        } else {
            log.info("objectNames:" + Arrays.toString(objectNames) + " removed successfully");
        }
    }

    /**
     * 判断桶是否存在
     *
     * @param bucketName 桶名称
     * @return 是否存在
     */
    @SneakyThrows
    public boolean isFoundBucket(String bucketName) {
        boolean res = minioClient.bucketExists(
                BucketExistsArgs.builder()
                        .bucket(bucketName)
                        .build());
        if (!res) {
            throw new BusinessException("桶不存在");
        }
        return true;
    }

    /**
     * 判断桶是否存在 不存在则创建
     *
     * @param bucketName bucketName
     */
    @SneakyThrows()
    public void bucketExistsAndCreate(String bucketName) {
        boolean found = isFoundBucket(bucketName);

        // 没有即自动创建一个bucket
        if (!found) {
            minioClient.makeBucket(
                    MakeBucketArgs.builder()
                            .bucket(bucketName)
                            .build());
        }
    }

    /**
     * 检查文件是否超出范围
     *
     * @param len      源文件大小
     * @param size     大小
     * @param byteUnit 单位
     */
    public void checkFileSize(Long len, int size, ByteUnit byteUnit) {
        String unit = byteUnit.getByteUnit();
        double fileSize = 0;
        if ("B".equalsIgnoreCase(unit)) {
            fileSize = (double) len;
        } else if ("K".equalsIgnoreCase(unit)) {
            fileSize = (double) len / 1024;
        } else if ("M".equalsIgnoreCase(unit)) {
            fileSize = (double) len / 1048576;
        } else if ("G".equalsIgnoreCase(unit)) {
            fileSize = (double) len / 1073741824;
        }

        if ((fileSize > size)) {
            throw new BusinessException("上传文件大小超出限制");
        }
    }

    /**
     * 判断是否为文件夹
     *
     * @param bucketName 桶名称
     * @param folderPath 文件路径
     * @return 是否为文件夹
     */
    public boolean folderExists(String bucketName, String folderPath) {
        ListObjectsArgs listObjectsArgs = ListObjectsArgs.builder()
                .bucket(bucketName).prefix(folderPath)
                .recursive(false).build();
        Iterable<Result<Item>> iterable = minioClient.listObjects(listObjectsArgs);
        // 不为空则说明是文件夹
        return iterable.iterator().hasNext();
    }

    /**
     * 在当前文件夹下获取所有文件名称
     *
     * @param bucketName 桶名称
     * @param folderPath 文件夹路径
     */
    public List<String> getAllObjectNameInFolder(String bucketName, String folderPath) {
        ListObjectsArgs listObjectsArgs = ListObjectsArgs.builder()
                .bucket(bucketName).prefix(folderPath)
                .recursive(true).build();

        Iterable<Result<Item>> results = minioClient.listObjects(listObjectsArgs);
        List<String> objectNameList = new ArrayList<>();
        for (Result<Item> item : results) {
            try {
                objectNameList.add(item.get().objectName());
            } catch (Exception e) {
                throw new RuntimeException("获取失败");
            }
        }
        return objectNameList;
    }

    /**
     * 创建文件夹
     *
     * @param dirName    文件夹名称
     * @param bucketName 桶名称
     */
    @SneakyThrows
    public String mkdir(String dirName, String bucketName) {
        ObjectWriteResponse response = minioClient.putObject(
                PutObjectArgs.builder()
                        .object(dirName + separator)
                        .bucket(bucketName).stream(new ByteArrayInputStream(new byte[]{}), 0, -1)
                        .build()
        );
        return response.etag();
    }

    /**
     * 迭代复制指定路径下的所有文件/目录
     *
     * @param bucketName bucket名称
     * @param minIoPath  需要复制的MinIo指定bucket下的路径,为""时即为下载bucket下的所有文件，格式示例："AA/BB/"
     * @param targetPath 复制到的目标路径
     */
    @SneakyThrows
    public void iterableCopyOfMinIoPath(String bucketName, String minIoPath, String targetPath) {
        Iterable<Result<Item>> itemLists =
                minioClient.listObjects(
                        ListObjectsArgs.builder()
                                .bucket(bucketName)
                                .prefix(minIoPath)
                                .recursive(false)
                                .build()
                );
        for (Result<Item> result : itemLists) {
            Item item = result.get();
            if (item.isDir()) {
                new File(targetPath + "\\" + bucketName + "\\" + item.objectName()).mkdirs();
                iterableCopyOfMinIoPath(bucketName, item.objectName(), targetPath);
            } else {
                InputStream inputStream = minioClient.getObject(GetObjectArgs.builder().bucket(bucketName)
                        .object(item.objectName()).build());
                FileOutputStream fileOutputStream = new FileOutputStream(targetPath + "\\" + bucketName + "\\" + item.objectName());
                int index;
                byte[] bytes = new byte[1024];
                while ((index = inputStream.read(bytes)) != -1) {
                    fileOutputStream.write(bytes, 0, index);
                    fileOutputStream.flush();
                }
                fileOutputStream.close();
                inputStream.close();
            }
        }
    }

}